package com.livew.framework.utils;

import java.io.IOException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.internal.bind.util.ISO8601Utils;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

public class GsonDateTypeAdapter extends TypeAdapter<Date> {

	private final Class<? extends Date> dateType;
	private final DateFormat enUsFormat;
	private final DateFormat localFormat;
	
	private String datePattern;

	public GsonDateTypeAdapter(Class<? extends Date> dateType) {
		 this(dateType,
			        DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US),
			        DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
	};

	public GsonDateTypeAdapter(Class<? extends Date> dateType, String datePattern) {
		this(dateType, new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern));
		this.datePattern  = datePattern;
	};
	
	public GsonDateTypeAdapter(final Class<? extends Date> dateType, DateFormat enUsFormat, DateFormat localFormat) {
		if ( dateType != Date.class && dateType != java.sql.Date.class && dateType != Timestamp.class ) {
		      throw new IllegalArgumentException("Date type must be one of " + Date.class + ", " + Timestamp.class + ", or " + java.sql.Date.class + " but was " + dateType);
		    }
		    this.dateType = dateType;
		    this.enUsFormat = enUsFormat;
		    this.localFormat = localFormat;
	};

	// These methods need to be synchronized since JDK DateFormat classes are
	// not thread-safe
	// See issue 162
	@Override
	public void write(JsonWriter out, Date value) throws IOException {
		String dateFormatAsString = XTDateUtil.dateToString(value, datePattern);
		out.value(dateFormatAsString);
	}

	@Override
	public Date read(JsonReader in) throws IOException {
		if (in.peek() != JsonToken.STRING) {
			throw new JsonParseException("The date should be a string value");
		}
		Date date = deserializeToDate(in.nextString());
		if (dateType == Date.class) {
			return date;
		} else if (dateType == Timestamp.class) {
			return new Timestamp(date.getTime());
		} else if (dateType == java.sql.Date.class) {
			return new java.sql.Date(date.getTime());
		} else {
			// This must never happen: dateType is guarded in the primary
			// constructor
			throw new AssertionError();
		}
	}

	private Date deserializeToDate(String s) {
		synchronized (localFormat) {
			try {
				return localFormat.parse(s);
			} catch (ParseException ignored) {
			}
			try {
				return enUsFormat.parse(s);
			} catch (ParseException ignored) {
			}
			try {
				return ISO8601Utils.parse(s, new ParsePosition(0));
			} catch (ParseException e) {
				throw new JsonSyntaxException(s, e);
			}
		}
	}
}
